package cn.com.atlasdata.atops.helper;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

/**
 * @author ZJH
 * @create 2018-09-25 10:44
 **/
public class ConnectionPoolManager {
    private static final Logger log = LoggerFactory.getLogger(ConnectionPoolManager.class);
    // 连接池存放
    private Map<String, DruidDataSource> pools = new HashMap<String, DruidDataSource>();

    // 连接池存放
    public Map<String, Long> m_pool_freetime = new HashMap<String, Long>();

    private final boolean cheakPool = true; // 是否定时检查连接池
    private final long lazyCheck = 1000 * 60 * 10;// 延迟多少时间后开始 检查
    private final long periodCheck = 1000 * 60 * 5;// 检查频率
    private final long timeForDestroy = 1000 * 60 * 20;// 空闲15分钟销毁连接池
    private final int initConnections = 2;// 初始化连接数
    private final long connectionTimeOut = 1000 * 90;// 连接超时时间，默认1分钟(-1无限)

    // 初始化
    private ConnectionPoolManager() {
        init();
    }

    // 单例实现
    public static ConnectionPoolManager getInstance() {
        return Singtonle.instance;
    }

    private static class Singtonle {

        private static ConnectionPoolManager instance = new ConnectionPoolManager();
    }

    // 初始化所有的连接池
    private void init() {
        log.debug("ConnectionPoolManager.init()");
        __cheackPool();
    }


    /***
     * 根据数据库连接信息，初始化连接池
     * @param poolName 连接池名称
     * @param dbtype 数据库类型
     * @param driverName 驱动类型
     * @param url 连接url
     * @param userName 用户名
     * @param passWord 密码
     * @return 数据库连接
     * @return 成功与否
     */

    private synchronized boolean initPool(String poolName,String dbtype,String driverName,String url,String userName,String passWord) {
        if (!pools.containsKey(poolName)) {
//            bean.setCheakPool(false);
            DruidDataSource ds = null;
            try {
                ds = new DruidDataSource();
                ds.setDriverClassName(driverName);
                // 基本属性 url、user、password
                ds.setUrl(url);
                ds.setDbType(dbtype);
                ds.setConnectProperties(new Properties());
                ds.setUsername(userName);
                ds.setPassword(passWord);

                // 配置初始化大小、最小、最大
                ds.setInitialSize(initConnections);
                ds.setMinIdle(initConnections);
                ds.setMaxActive(initConnections);

                // 配置获取连接等待超时的时间
                ds.setMaxWait(connectionTimeOut);

                // 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
                ds.setTimeBetweenEvictionRunsMillis(periodCheck);


                // 配置一个连接在池中最小生存的时间，单位是毫秒
                ds.setMinEvictableIdleTimeMillis(lazyCheck);
                ds.setTimeBetweenEvictionRunsMillis(60000);
                ds.setMinEvictableIdleTimeMillis(300000);

                // 建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。
                ds.setTestWhileIdle(true);
                //设置出错时不自动重连，每个作业重连数据库的次数可能不同，连接池不进行重连次数控制
                ds.setConnectionErrorRetryAttempts(0);
                // 设置出错后马上返回错误
                ds.setFailFast(true);
                // 设置连接出错不再获取连接
                ds.setNotFullTimeoutRetryCount(-1);
                // 申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。
                ds.setTestOnBorrow(true);
                /** BreakAfterAcquireFailure
                 *  true表示pool向数据库请求连接失败后标记整个pool为block并close,就算后端数据库恢复正常也不进行重连,客户端对pool的请求都拒绝掉.
                 *  false表示不会标记 pool为block,新的请求都会尝试去数据库请求connection.默认为false.
                 *  因此,如果想让数据库和网络故障恢复之后,pool能继续请求正常资源必须把此项配置设为false
                 */
                // ds.setBreakAfterAcquireFailure(true);
                ds.setValidationQueryTimeout(60);
                ds.setValidationQuery("select 1 ");

            } catch (Exception e) {
                log.error("Init connection fail" );
                log.error(e.getMessage(), e);
                throw new RuntimeException(e);
            }

            pools.put(poolName, ds);
            log.info("Init connection successed" );
        }
        return true;
    }


    /***
     * 获得连接,根据连接池名字 获得连接
     * @param poolName 连接池名称
     * @param dbtype 数据库类型
     * @param driverName 驱动类型
     * @param url 连接url
     * @param userName 用户名
     * @param passWord 密码
     * @return 数据库连接
     * @throws SQLException
     */
    public Connection getConnection(String poolName,String dbtype,String driverName,String url,String userName,String passWord) throws SQLException {
        Connection conn = null;
        try {
            if (!pools.containsKey(poolName)) {
                initPool(poolName,dbtype,driverName,url,userName,passWord);
            }

            if (pools.size() > 0 && pools.containsKey(poolName)) {
                conn = getPool(poolName).getConnection();
                m_pool_freetime.remove(poolName);
            } else {
                log.error("Can't find this connecion pool " );
            }
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
            SQLException tempE = e;
            while (null != (tempE = tempE.getNextException())) {
                log.error(tempE.getMessage(), tempE);
            }
            throw e;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw e;
        }
        return conn;
    }


    /***
     * 关闭，回收连接
     * @param poolName
     * @param conn
     */
    public void close(String poolName, Connection conn) {
        if (conn == null) {
            DruidDataSource ds = getPool(poolName);
            if (ds != null) {
                if (ds.getActiveCount() == 0) {
                    m_pool_freetime.put(poolName, System.currentTimeMillis());
                }
            }
            return;
        }
        try {
            conn.close();
            conn = null;
        } catch (SQLException e) {
            log.error("close connection fail!->" + e.getMessage(), e);
        }
        DruidDataSource ds = getPool(poolName);
        if (ds != null) {
            if (ds.getActiveCount() == 0) {
                m_pool_freetime.put(poolName, System.currentTimeMillis());
            }
        }
    }

    /***
     * 清空连接池
     * @param poolName
     */
    public synchronized void destroy(String poolName) {
        DruidDataSource pool = pools.remove(poolName);
        m_pool_freetime.remove(poolName);
        if (pool != null) {
            pool.close();
            pool = null;
        }
    }

    /***
     * 获得连接池
     * @param poolName
     * @return
     */
    public DruidDataSource getPool(String poolName) {
        DruidDataSource pool = null;
        if (pools.size() > 0) {
            pool = pools.get(poolName);
        }
        return pool;
    }

    /***
     * 检查连接池情况
     * @param poolName
     */
    public void cheackPool(String poolName) {
        DruidDataSource pool = getPool(poolName);
        if (pool == null) {
            m_pool_freetime.remove(poolName);
        } else {
            long destroytime = System.currentTimeMillis() - timeForDestroy;
            if (m_pool_freetime.get(poolName) == null) {

            } else if (m_pool_freetime.get(poolName) < destroytime) {
                destroy(poolName);
            } else if (pool.getActiveCount() == 0) {
                m_pool_freetime.put(poolName, System.currentTimeMillis());
            }
        }
    }

    /***
     * 定时检查连接池情况
     */
    private void __cheackPool() {
        if (cheakPool) {
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    long destroytime = System.currentTimeMillis() - timeForDestroy;
                    Iterator<Map.Entry<String, DruidDataSource>> it = pools.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry<String, DruidDataSource> entry = it.next();
                        String poolName = entry.getKey();
                        DruidDataSource ds = entry.getValue();
                        if (ds == null) {
                            // 移除没有初始化的连接池对象指针
                            it.remove(); // OK
                            m_pool_freetime.remove(poolName);
                        } else if (ds.getActiveCount() > 0) {
                            // 如果还有活动链接（在使用的连接），不校验
                            m_pool_freetime.remove(poolName);
                        } else if (m_pool_freetime.get(poolName) == null) {
                            // 如果还没有记录空闲时间的连接池，记录空闲时间为当前时间
                            m_pool_freetime.put(poolName, System.currentTimeMillis());
                        } else {
                            if (m_pool_freetime.get(poolName) < destroytime) {
                                // 连接池空闲不使用超过时间限制，销毁连接池
                                it.remove(); // OK
                                m_pool_freetime.remove(poolName);
                                try {
                                    ds.close();
                                    ds = null;
                                } catch (Throwable t) {
                                    log.error("close DataSource fail!->" + t.getMessage(), t);
                                }
                            }
                        }
                    }

                    // 清楚连接池的空闲时间记录中无用的时间值
                    Iterator<Map.Entry<String, Long>> it2 = m_pool_freetime.entrySet().iterator();
                    while (it2.hasNext()) {
                        Map.Entry<String, Long> entry = it2.next();
                        String poolName = entry.getKey();
                        if (entry.getValue() == null) {
                            it2.remove(); // OK
                        } else if (!pools.containsKey(poolName)) {
                            it2.remove(); // OK
                        }
                    }
                }
            }, lazyCheck, periodCheck);
        }
    }
}
